package com.immediatus.engine.image;

import com.immediatus.engine.Unit;
import com.immediatus.engine.shape.Shape;
import com.immediatus.graphics.buffer.RectangleBatchTextureRegionBuffer;
import com.immediatus.graphics.camera.Camera;
import com.immediatus.graphics.contracts.ITexture;
import com.immediatus.graphics.texture.region.BaseTextureRegion;
import com.immediatus.graphics.util.OpenGLWrapper;
import com.immediatus.graphics.vertex.RectangleBatchVertexBuffer;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

public class ImageBatch extends Unit{

    private int _index;
	private int _vertices;
    private final int _capacity;
	private int _sourceBlendFunction;
	private int _destinationBlendFunction;

    private final ITexture _texture;
	private final RectangleBatchVertexBuffer _rectangleBatchVertexBuffer;
	private final RectangleBatchTextureRegionBuffer _rectangleBatchTextureRegionBuffer;

	public ImageBatch(final ITexture texture_, final int capacity_) {
		this(texture_, capacity_, new RectangleBatchVertexBuffer(capacity_, GL11.GL_STATIC_DRAW, true), new RectangleBatchTextureRegionBuffer(capacity_, GL11.GL_STATIC_DRAW, true));
	}

	public ImageBatch(final ITexture texture_, final int capacity_, final RectangleBatchVertexBuffer vertexBuffer_, final RectangleBatchTextureRegionBuffer textureRegionBuffer_) {
		this._texture = texture_;
		this._capacity = capacity_;
		this._rectangleBatchVertexBuffer = vertexBuffer_;
		this._rectangleBatchTextureRegionBuffer = textureRegionBuffer_;

		this.initBlendFunction();
	}

	public void setBlendFunction(final int sourceBlendFunction_, final int destinationBlendFunction_) {
		this._sourceBlendFunction = sourceBlendFunction_;
		this._destinationBlendFunction = destinationBlendFunction_;
	}

	public int getIndex() {
		return this._index;
	}

	public void setIndex(final int index_) {
		this.assertCapacity(index_);
		this._index = index_;
		final int vertexIndex = index_ * 2 * RectangleBatchVertexBuffer.VERTICES_PER_RECTANGLE;
		this._rectangleBatchVertexBuffer.setIndex(vertexIndex);
		this._rectangleBatchTextureRegionBuffer.setIndex(vertexIndex);
	}

	@Override
	protected void doDraw(final GL10 gl_, final Camera camera_) {
		this.onInitDraw(gl_);
		this.begin(gl_);
		this.onApplyVertices(gl_);
		this.onApplyTextureRegion(gl_);
		this.drawVertices(gl_, camera_);
		this.end(gl_);
	}

	@Override
	public void reset() {
		super.reset();
		this.initBlendFunction();
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();

		if(this._rectangleBatchVertexBuffer.isManaged()) {
			this._rectangleBatchVertexBuffer.unloadFromActiveBufferObjectManager();
		}
		if(this._rectangleBatchTextureRegionBuffer.isManaged()) {
			this._rectangleBatchTextureRegionBuffer.unloadFromActiveBufferObjectManager();
		}
	}

	protected void begin(final GL10 gl_) {
        OpenGLWrapper.disableDepthMask(gl_);
	}

	protected void end(final GL10 gl_) {
        OpenGLWrapper.enableDepthMask(gl_);
	}

	public void draw(final BaseTextureRegion textureRegion_, final float x_, final float y_, final float w_, final float h_){
        this.assertCapacity();
		this.assertTexture(textureRegion_);
		this._rectangleBatchVertexBuffer.add(x_, y_, w_, h_);
		this._rectangleBatchTextureRegionBuffer.add(textureRegion_);
		this._index++;
	}

	public void draw(final BaseTextureRegion textureRegion_, final float x_, final float y_, final float w_, final float h_, final float rotation_) {
		this.assertCapacity();
		this.assertTexture(textureRegion_);
		this._rectangleBatchVertexBuffer.add(x_, y_, w_, h_, rotation_);
		this._rectangleBatchTextureRegionBuffer.add(textureRegion_);
		this._index++;
    }

	public void draw(final BaseTextureRegion textureRegion_, final float x_, final float y_, final float w_, final float h_, final float scaleX_, final float scaleY_) {
		this.assertCapacity();
		this.assertTexture(textureRegion_);
		this._rectangleBatchVertexBuffer.add(x_, y_, w_, h_, scaleX_, scaleY_);
		this._rectangleBatchTextureRegionBuffer.add(textureRegion_);
        this._index++;
    }

	public void draw(final BaseTextureRegion textureRegion_, final float x_, final float y_, final float w_, final float h_, final float rotation_, final float scaleX_, final float scaleY_) {
		this.assertCapacity();
		this.assertTexture(textureRegion_);
		this._rectangleBatchVertexBuffer.add(x_, y_, w_, h_, rotation_, scaleX_, scaleY_);
		this._rectangleBatchTextureRegionBuffer.add(textureRegion_);
        this._index++;
	}

    public void draw(final BaseTextureRegion textureRegion_, final float x1_, final float y1_, final float x2_, final float y2_, final float x3_, final float y3_, final float x4_, final float y4_) {
		this.assertCapacity();
		this.assertTexture(textureRegion_);
		this._rectangleBatchVertexBuffer.addInner(x1_, y1_, x2_, y2_, x3_, y3_, x4_, y4_);
        this._rectangleBatchTextureRegionBuffer.add(textureRegion_);
		this._index++;
	}

    public void draw(final BaseImageLabel imageLabel_) {
		if(imageLabel_.isVisible()){
            this.assertCapacity();

            final BaseTextureRegion textureRegion = imageLabel_.getTextureRegion();
			this.assertTexture(textureRegion);

			if(imageLabel_.getRotation() == 0 && !imageLabel_.isScaled()) {
				this._rectangleBatchVertexBuffer.add(imageLabel_.getX(), imageLabel_.getY(), imageLabel_.getWidth(), imageLabel_.getHeight());
			} else {
				this._rectangleBatchVertexBuffer.add(imageLabel_.getWidth(), imageLabel_.getHeight(), imageLabel_.getLocalToParentTransformation());
            }

            this._rectangleBatchTextureRegionBuffer.add(textureRegion);
            this._index++;
        }
    }

	public void submit() {
		this.onSubmit();
	}

	private void onSubmit() {
		this._vertices = this._index * RectangleBatchVertexBuffer.VERTICES_PER_RECTANGLE;
		this._rectangleBatchVertexBuffer.submit();
		this._rectangleBatchTextureRegionBuffer.submit();

        this._index = 0;
		this._rectangleBatchVertexBuffer.setIndex(0);
		this._rectangleBatchTextureRegionBuffer.setIndex(0);
	}

	private void initBlendFunction() {
		if(this._texture.getTextureOptions()._PreMultipyAlpha) {
			this.setBlendFunction(Shape.BLENDFUNCTION_SOURCE_PREMULTIPLYALPHA_DEFAULT, Shape.BLENDFUNCTION_DESTINATION_PREMULTIPLYALPHA_DEFAULT);
		}
	}

	protected void onInitDraw(final GL10 gl_) {
		OpenGLWrapper.setColor(gl_, this.getRed(), this.getGreen(), this.getBlue(), this.getAlpha());

        OpenGLWrapper.enableVertexArray(gl_);
        OpenGLWrapper.blendFunction(gl_, this._sourceBlendFunction, this._destinationBlendFunction);

        OpenGLWrapper.enableTextures(gl_);
        OpenGLWrapper.enableTexCoordArray(gl_);
	}

	protected void onApplyVertices(final GL10 gl_) {
		if(OpenGLWrapper.EXTENSIONS_VERTEXBUFFEROBJECTS) {
			final GL11 gl11 = (GL11)gl_;

			this._rectangleBatchVertexBuffer.selectOnHardware(gl11);
            OpenGLWrapper.vertexZeroPointer(gl11);
		} else {
            OpenGLWrapper.vertexPointer(gl_, this._rectangleBatchVertexBuffer.getFloatBuffer());
		}
	}

	private void onApplyTextureRegion(final GL10 gl_) {
		if(OpenGLWrapper.EXTENSIONS_VERTEXBUFFEROBJECTS) {
			final GL11 gl11 = (GL11)gl_;

			this._rectangleBatchTextureRegionBuffer.selectOnHardware(gl11);

			this._texture.bind(gl_);
            OpenGLWrapper.texCoordZeroPointer(gl11);
		} else {
			this._texture.bind(gl_);
            OpenGLWrapper.texCoordPointer(gl_, this._rectangleBatchTextureRegionBuffer.getFloatBuffer());
		}
	}

	private void drawVertices(final GL10 gl_, final Camera camera_) {
		gl_.glDrawArrays(GL10.GL_TRIANGLES, 0, this._vertices);
	}

	private void assertCapacity(final int index_) {
		if(index_ >= this._capacity) {
			throw new IllegalStateException("This supplied index: '" + index_ + "' is exceeding the capacity: '" + this._capacity + "' of this SpriteBatch.");
		}
	}

	private void assertCapacity() {
		if(this._index == this._capacity) {
			throw new IllegalStateException("This SpriteBatch has already reached its capacity (" + this._capacity + ").");
		}
	}

	protected void assertTexture(final BaseTextureRegion textureRegion_) {
		if(textureRegion_.getTexture() != this._texture) {
			throw new IllegalArgumentException("The supplied Texture does match the Texture of this SpriteBatch.");
		}
	}
}
